import * as THREE from 'three';
import * as loader from './loader.js';
import _ from 'lodash';
import Base from './Base.js';
import Storage from '../../Storage.js';
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';

let ACTORS = {};
let ACTORANIMATIONS = {};
class Actor extends Base {
    constructor(root) {
        super();
        this.root = root;
        this.staticPath = root.staticPath;
        // this._cache = Storage.getCacheStorage('ACTORS');
        // this._animations_cache = Storage.getCacheStorage('ACTORANIMATIONS');
        this._cache = ACTORS;
        this._animations_cache = ACTORANIMATIONS;
        this.preAction = null;
        this.action = null;
        this.actionName = '';
        // this.actionMesh = null;
        this.actions = {};
        this.clock = new THREE.Clock();
        this.mixer = null;
        this.instancedMesh = false;
        return this;
    }

    destory() {
        if (this.action) this.action.fadeOut(0).stop();
        // if (this._cache) this._cache.clear();
        ACTORS = {};
        ACTORANIMATIONS = {};
        super.destory();
    }
    static clearCached() {
        // Storage.getCacheStorage('ACTORS').clear();
        ACTORS = {};
        ACTORANIMATIONS = {};
    }

    // load from cache or oss
    async init(id, key, castShadow) {
        if (!key) return;
        // let o = this._cache.get(key);
        let o = this._cache[key];
        if (!this.meshName) this.meshName = key.split('.')[0];
        if (!o) {
            // console.log('[Actor] init: ', key);
            let f = `${this.staticPath}/models/${key}`;
            let gltf = await loader.loadGltfAsync(f).catch(e => { //eslint-disable-line
                console.error('Actor.init.error: ', e);
            });
            if (!gltf || !gltf.scene) return;
            // if (gltf && gltf.scene) o = gltf.scene.clone();
            if (gltf && gltf.scene) o = SkeletonUtils.clone(gltf.scene);
            o.rotation.x = Math.PI / 2;
            o.traverse(child => (child.isMesh ? (child.castShadow = castShadow) : undefined));
            // this._cache.set(key, o);
            if (this._cache) this._cache[key] = o;
            if (this._animations_cache) this._animations_cache[key] = gltf.animations;
        }
        if (!o && !this._animations_cache[key]) return;
        o = SkeletonUtils.clone(o);
        this.animations = this._animations_cache[key];
        this.model = o;
        this.mixer = this.getCurrentMixer(id, o);
        if (!this.mixer) return o;
        // this.mixer.setTime(0);
        this.actions = {};
        this.animations.forEach(clip => (this.actions[clip.name] = this.mixer.clipAction(clip.clone())));
        return o;
    }
    /**
     * Get cached InstancedMesh, if count number unequal to the instance's count, redo itsself. 
     * @param {*} key cached key
     * @param {*} castShadow castShadow or not
     * @param {*} count Actually instance quantity
     * @param {*} idx Index of an instance
     */
    // async initInstance(id, key, castShadow, count, idx) {
    //     if (!key) return;
    //     // let o = this._cache.get(key);
    //     let o = this._cache[key];
    //     if (!this.meshName) this.meshName = key.split('.')[0];
    //     if (!o) {
    //         let f = `${this.staticPath}/models/${key}`;
    //         let gltf = await loader.loadGltfAsync(f).catch(e => { //eslint-disable-line
    //             console.error('Actor.init.error: ', e);
    //         });
    //         if (!gltf || !gltf.scene) return;

    //         const _mainMesh = gltf.scene.getObjectByName('main');
    //         if (_mainMesh) {
    //             this.instancedMesh = true;
    //             let meshes = [];
    //             if (_mainMesh.isMesh) meshes.push(_mainMesh);
    //             else _mainMesh.traverse(c => (c.isMesh ? meshes.push(c) : undefined));
    //             // let clonedScene = gltf.scene.clone();
    //             // clonedScene.traverse(c => (c.isMesh ? meshes.push(c) : undefined));
    //             // console.log('!@#@!: ', key, idx, gltf.scene, meshes);
    //             o = [];
    //             meshes.forEach(m => {
    //                 let geo = new THREE.InstancedBufferGeometry();
    //                 THREE.BufferGeometry.prototype.copy.call(geo, m.geometry);
    //                 let met = new THREE.InstancedMesh(geo, m.material, count);
    //                 met.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    //                 met.instanceMatrix.needsUpdate = true;
    //                 met.castShadow = castShadow;
    //                 met.name = m.name;
    //                 met.userData = m.userData;
    //                 met.userData.m3d = key;
    //                 o.push(met);
    //             });
    //             // console.log('!@#@! --->  ', key, idx, o);
    //         } else {
    //             o = gltf.scene.clone();
    //             o.rotation.x = Math.PI / 2;
    //             o.traverse(child => (child.isMesh ? (child.castShadow = castShadow) : undefined));
    //         }

    //         // this._cache.set(key, o);
    //         this._cache[key] = o;
    //         this.animations = gltf.animations;
    //         // this._animations_cache.set(key, gltf.animations);
    //         this._animations_cache[key] = gltf.animations;
    //     } else {
    //         if (!Array.isArray(o) && typeof o.clone === 'function') o = o.clone();
    //         if (Array.isArray(o)) this.instancedMesh = true;
    //         // this.animations = this._animations_cache.get(key);
    //         // let as = this._animations_cache.get(key);
    //         let as = this._animations_cache[key];
    //         this.animations = [];
    //         (as || []).forEach(clip => this.animations.push(clip.clone()));
    //     }
    //     this.model = o;
    //     this.mixer = this.getCurrentMixer(id, this.model);
    //     // this.mixer.setTime(0);
    //     this.actions = {};
    //     // this.animations.forEach(clip => (this.actions[clip.name] = clip.clone()));
    //     if (!this.mixer) return o;
    //     this.animations.forEach(clip => (this.actions[clip.name] = this.mixer.clipAction(clip.clone())));
    //     return o;
    // }
    async initInstance(key, castShadow, count, idx) {
        if (!key) return;
        // let o = this._cache.get(key);
        let o = this._cache[key];
        if (!this.meshName) this.meshName = key.split('.')[0];
        if (!o) {
            let f = `${this.staticPath}/models/${key}`;
            let gltf = await loader.loadGltfAsync(f).catch(e => { //eslint-disable-line
                console.error('Actor.init.error: ', e);
            });
            if (!gltf || !gltf.scene) return;
            let mesh = gltf && gltf.scene && SkeletonUtils.clone(gltf.scene);
            let meshes = [];
            if (mesh.isMesh) meshes.push(mesh);
            else {
                mesh.traverse(c => {
                    let p = c.parent;
                    // let tag = c.name.match(/alarm|warning|signal/);
                    // if (!tag && p) tag = p.name.match(/alarm|warning|signal/);
                    let tag = c.name.match(/alarm|warning/);
                    if (!tag && p) tag = p.name.match(/alarm|warning/);
                    if (tag) return;
                    c.isMesh && meshes.push(c);
                });
            }
            // console.log('Actor.initInstance: ', key, mesh, meshes);

            o = [];
            meshes.forEach(m => {
                let geo = new THREE.InstancedBufferGeometry();
                THREE.BufferGeometry.prototype.copy.call(geo, m.geometry);
                let met = new THREE.InstancedMesh(geo, m.material, count);
                if (m.parent && m.parent.position && m.parent.position.y) {
                    met.position.z = m.parent.position.y;
                }
                met.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
                met.instanceMatrix.needsUpdate = true;
                met.castShadow = castShadow;
                met.name = m.name;
                met.userData = m.userData;
                met.userData.m3d = key;
                o.push(met);
            });
            // this._cache.set(key, o);
            this._cache[key] = o;
            this.animations = gltf.animations;
            // this._animations_cache.set(key, gltf.animations);
            this._animations_cache[key] = gltf.animations;
        } else {
            if (!Array.isArray(o) && typeof o.clone === 'function') o = o.clone();
            if (Array.isArray(o)) this.instancedMesh = true;
            // this.animations = this._animations_cache.get(key);
            // let as = this._animations_cache.get(key);
            let as = this._animations_cache[key];
            this.animations = [];
            (as || []).forEach(clip => this.animations.push(clip.clone()));
        }
        this.model = o;
        this.mixer = this.getCurrentMixer(key, this.model);
        // this.mixer.setTime(0);
        this.actions = {};
        // this.animations.forEach(clip => (this.actions[clip.name] = clip.clone()));
        if (!this.mixer) return o;
        this.animations.forEach(clip => (this.actions[clip.name] = this.mixer.clipAction(clip.clone())));
        return o;
    }

    setStaticPath(path) {
        this.staticPath = path;
    }
    getMesh(meshName) {
        let mesh = null;
        if (Array.isArray(this.model)) {
            mesh = this.model.find(v => v.name === meshName);
        } else mesh = this.model.getObjectByName(meshName);
        return mesh;
    }
    getCurrentMixer(id, model) {
        if (this.mixer) return this.mixer;
        if (!this.root.director) return;
        const mixer = this.root.director.initAnimationMixer(model, id);
        return mixer;
    }
    getClip(act) {
        const clip = THREE.AnimationClip.findByName(this.animations, act);
        return clip;
    }
    play(id, act, meshName, type) {
        if (id && !act && !meshName) return this.stop(id);

        if (!this.actions || !this.actions[act]) return;

        if (!this.root.director) return;
        if (meshName) this.meshName = meshName;
        if (/alarm[0-9]/.test(this.meshName)) this.meshName = 'alarm-light';

        // if (this.action && this.action.isRunning()) return;

        // this.actionMesh = this.getMesh(this.meshName);

        if (this.action && this.actionName !== act) {
            this.preAction = this.action;
            this.preAction.fadeOut(0.2).stop().reset();
            this.action = this.actions[act];
            this.action.reset().fadeIn(0.2).play();
            this.actionName = act; // store current animation name
        }

        if (!this.action) {
            this.action = this.actions[act];
            this.action.reset().fadeIn(0.2).play();
            this.actionName = act; // store current animation name
        }

        // if (this.action) {
        //     // this.action.setLoop(THREE.LoopOnce); // THREE.LoopOnce, THREE.LoopRepeat, THREE.LoopPingPong
        //     this.action.reset().fadeIn().play();
        //     this.actionName = act; // store current animation name
        //     this.mixer.addEventListener('finished', () => this.restoreState.call(this, this.action));
        // }
    }
    restoreState() {
        this.mixer.removeEventListener('finished', () => this.restoreState.call(this, this.action));
        // console.log('[Actor] restoreState: ', ...arguments);
        this.action.fadeOut(0.2).reset().stop();
        this.stop();
    }
    stop(id) {
        // if (!this.action) this.actionName = null; // clearn animation name
        if (this.mixer) {
            this.mixer.stopAllAction();
        }
        // if (this.action) this.action = null;
        // if (this.actionName) this.actionName = null; // clearn animation name
    }
    playInstance(id, group, idx, color) {
        if (id && !color) color = new THREE.Color(0xffffff);
        // console.warn('...> ', id, idx, color);
        group.traverse(m => {
            if (!m.isInstancedMesh) return;
            m.setColorAt(idx, color);
            m.instanceColor.needsUpdate = true;
        });
    }
    stopInstance(id, group, idx) {
        group.traverse(m => {
            if (!m.isInstancedMesh) return;
            m.setColorAt(idx, color);
            m.instanceColor.needsUpdate = true;
        });
    }
}
export default Actor;
